/* Copyright (C) 2004 - 2008  Versant Inc.  http://www.db4o.com

This file is part of the sharpen open source java to c# translator.

sharpen is free software; you can redistribute it and/or modify it under
the terms of version 2 of the GNU General Public License as published
by the Free Software Foundation and as clarified by db4objects' GPL 
interpretation policy, available at
http://www.db4o.com/about/company/legalpolicies/gplinterpretation/
Alternatively you can write to db4objects, Inc., 1900 S Norfolk Street,
Suite 350, San Mateo, CA 94403, USA.

sharpen is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
for more details.

You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
59 Temple Place - Suite 330, Boston, MA  02111-1307, USA. */

/* Copyright (C) 2006 Versant Inc. http://www.db4o.com */

package sharpen.core;

import org.eclipse.jdt.core.dom.*;
import sharpen.core.csharp.ast.*;

/**
 * @exclude
 */
public abstract class AbstractNestedClassBuilder extends CSharpBuilder {

    private boolean _usesEnclosingMember;

    public AbstractNestedClassBuilder(CSharpBuilder other) {
        super(other);
    }

    protected abstract ITypeBinding nestedTypeBinding();

    @Override
    protected CSExpression mapMethodTargetExpression(MethodInvocation node) {
        if (null == node.getExpression()) {
            return createEnclosingTargetReferences(node.getName());
        }
        return super.mapMethodTargetExpression(node);
    }

    public boolean visit(ThisExpression node) {
        if (null == node.getQualifier()) return super.visit(node);
        pushExpression(createEnclosingThisReference(node.getQualifier().resolveTypeBinding(), true));
        return false;
    }

    public boolean visit(SimpleName name) {
        if (isInstanceFieldReference(name)) {
            pushExpression(createEnclosingReferences(name));
            return false;
        }
        return super.visit(name);
    }

    private boolean isInstanceFieldReference(SimpleName name) {
        IBinding binding = name.resolveBinding();
        if (IBinding.VARIABLE != binding.getKind()) return false;
        return ((IVariableBinding) binding).isField();
    }

    private CSExpression createEnclosingReferences(SimpleName name) {
        CSExpression target = createEnclosingTargetReferences(name);
        return new CSMemberReferenceExpression(target, mappedName(name));
    }

    private CSExpression createEnclosingTargetReferences(SimpleName name) {
        ITypeBinding enclosingClassBinding = getDeclaringClass(name);

        CSExpression target = isStaticMember(name)
                ? createTypeReference(enclosingClassBinding)
                : createEnclosingThisReference(enclosingClassBinding);
        return target;
    }

    private CSExpression createEnclosingThisReference(ITypeBinding enclosingClassBinding) {
        return createEnclosingThisReference(enclosingClassBinding, false);
    }

    private CSExpression createEnclosingThisReference(
            ITypeBinding enclosingClassBinding, boolean ignoreSuperclass) {
        CSExpression enclosing = new CSThisExpression();
        ITypeBinding binding = nestedTypeBinding();
        ITypeBinding to = enclosingClassBinding;
        while (binding.getErasure() != to.getErasure() && (ignoreSuperclass || !isSuperclass(binding, to))) {
            requireEnclosingReference();
            enclosing = new CSMemberReferenceExpression(enclosing, "_enclosing");
            binding = binding.getDeclaringClass();
            if (null == binding) break;
        }
        return enclosing;
    }

    protected boolean isEnclosingReferenceRequired() {
        return _usesEnclosingMember;
    }

    protected void requireEnclosingReference() {
        _usesEnclosingMember = true;
    }

    private String mappedName(SimpleName name) {
        IBinding binding = name.resolveBinding();
        return binding instanceof IMethodBinding ? mappedMethodName((IMethodBinding) binding) : identifier(name);
    }

    private boolean isStaticMember(SimpleName name) {
        return Modifier.isStatic(name.resolveBinding().getModifiers());
    }

    private boolean isSuperclass(ITypeBinding type, ITypeBinding candidate) {
        ITypeBinding superClass = type.getSuperclass().getTypeDeclaration();
        candidate = candidate.getTypeDeclaration();
        while (null != superClass) {
            if (superClass == candidate) {
                return true;
            }
            superClass = superClass.getSuperclass();
        }
        return false;
    }

    private ITypeBinding getDeclaringClass(Name reference) {
        IBinding binding = reference.resolveBinding();
        switch (binding.getKind()) {
            case IBinding.METHOD: {
                return ((IMethodBinding) binding).getDeclaringClass().getTypeDeclaration();
            }
            case IBinding.VARIABLE: {
                IVariableBinding variable = (IVariableBinding) binding;
                return variable.getDeclaringClass().getTypeDeclaration();
            }
        }
        //throw new UnsupportedOperationException();
        return null;
    }

    protected CSField createEnclosingField() {
        return CSharpCode.newPrivateReadonlyField("_enclosing", enclosingTypeReference());
    }

    private CSTypeReference enclosingTypeReference() {
        CSTypeReference tr = new CSTypeReference(_currentType.name());
        for (CSTypeParameter tp : _currentType.typeParameters())
            tr.addTypeArgument(new CSTypeReference(tp.name()));
        return tr;
    }

    protected CSInfixExpression createFieldAssignment(String fieldName, String rvalue) {
        return createFieldAssignment(fieldName, new CSReferenceExpression(rvalue));
    }

    protected CSInfixExpression createFieldAssignment(String fieldName,
                                                      final CSExpression fieldValue) {
        CSExpression fieldReference = new CSMemberReferenceExpression(new CSThisExpression(), fieldName);
        return new CSInfixExpression("=", fieldReference, fieldValue);
    }

}